#include "i2c_soft.h"
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>

// 超时次数
static int TIMEOUT = 50000;

// SCL引脚相关
#define  SCL(bit) 	            gpio_bit_write(I2C_SCL_PORT, I2C_SCL_PIN, bit == 1? SET : RESET)
#define  GET_SCL_PIN_STATE()    gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN)

// SDA引脚相关
#define  SDA(bit) 	            gpio_bit_write(I2C_SDA_PORT, I2C_SDA_PIN, bit == 1? SET : RESET)
#define  GET_SDA_PIN_STATE()    gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN)

// 修改SDA输入输出模式 --默认是输出 SDA在不通场景下 由不同设备控制
#define  SET_SDA_MODE_OUT()       gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, I2C_SDA_PIN);
#define  SET_SDA_MODE_IN()        gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, I2C_SDA_PIN);
#define DELAY()  delay_1us(5)

/**
 * @desc  : 初始化
 * @date  : 2024-11-21
 * @author: AnRui
 */
void i2c_soft_init() {
    rcu_periph_clock_enable(I2C_SCL_RCU);
    gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, I2C_SCL_PIN);
    gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_MAX, I2C_SCL_PIN);
    SCL(1);

    rcu_periph_clock_enable(I2C_SDA_RCU);
    gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, I2C_SDA_PIN);
    gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_MAX, I2C_SDA_PIN);
    SDA(1);
}


/**
 * @desc  : 主设备向从设备发送开始信号
 *          ______
 *         |      |
 *  SCL ___|      |___
 *       ______
 *      |      |
 *  SDA |      |____
 *
 * @date  : 2024-11-19
 * @author: AnRui
 */
static void i2c_soft_start() {
    SET_SDA_MODE_OUT(); // 设置输出模式 是为了避免其他操作 修改了模式
    // SCL(0);  todo 为什么不先把 scl 拉低呢 ？
    // 开始 主要的是sda的下降沿 即便 scl一开始 是高也没关系  不会触发开始
    SDA(1);
    DELAY();

    SCL(1);
    DELAY();

    SDA(0);
    DELAY();

    SCL(0);
    DELAY();
}

/**
 * @desc  : 主设备向从设备发送结束信号
 *        _________
 *       |
 *  SCL _|
 *           ______
 *          |
 *  SDL ____|
 *
 * @date  : 2024-11-19
 * @author: AnRui
 */
static void i2c_soft_stop() {
    SET_SDA_MODE_OUT();

    SCL(0);
    SDA(0);

    SCL(1);
    DELAY();
    SDA(1);
    DELAY();

}

/**
 * @desc  : 发送数据
 *           ____
 *          |    |
 *  SCL ____|    |____
 *         ________
 *        |        |
 *  SDA __|        |__   BIT 1
 *
 *  SDA __          __   BIT 0
 *        |        |
 *        |________|
 *
 * @date  : 2024-11-19
 * @author: AnRui
 * @param :  data - {参数说明}:
 */
void i2c_soft_send_data(uint8_t data) {
    uint8_t i;
    SET_SDA_MODE_OUT();  // 调整为输出模式就相当于把SDA拉低了

    for (i = 0;i < 8;i++) {
        //发送方法主要集中在这 每次根据最高位来发 下面依次把低位移到高位上去
        if (data & 0x80) {
            SDA(1);
        } else {
            SDA(0);
        }
        DELAY();
        SCL(1);
        DELAY();
        SCL(0);
        DELAY();
        data <<= 1;
    }
}

/**
 * @desc  : 等待响应
 *
 *             ____
 *            |    |
 *  SCL ______|    |_______
 *         ___       ______
 *        |   *     |
 *  SDA __|   ******|
 *
 *  注意: *位置代码从设备拉低
 * @date  : 2024-11-19
 * @author: AnRui
 */
static uint8_t i2c_soft_wait_ack() {
    // 主到从， 从到主
    // 主动权：
    // 主到从： send，主设备拿到pin，设置pin高低电平（输出）
    // 从到主： wait_ack，从设备拿到pin，从设备pin高低电平，主设备读取高低电平（输入）
    SCL(0);
    SDA(1);
    DELAY();

    // SDA交出控制权，SDA变为输入 TODO
    SET_SDA_MODE_IN();

    SCL(1);
    DELAY();
    // 读不到对方的应答：
    // 软件：1. 有没有重试机制 2. 地址
    // 硬件：2. 芯片是否焊好
    // 读取SDA的状态
    uint8_t retry = 10;
    while (GET_SDA_PIN_STATE() == 1 && retry > 0) {
        retry--;
        DELAY();
    }
    if (retry <= 0) {
        i2c_soft_stop();
        return 1;
    } else {
        SCL(0);
        SET_SDA_MODE_OUT();
    }
    return 0;
}



/**
 * @desc  : 接收数据
 *          ______
 *         |      |
 *  SCL ___|      |___
 *        ___________
 *       |           |
 *  SDL  |           |  BIT1
 *
 *       |           |
 *  SDL  |           |  BIT0
 *        ———————————
 *
 * @date  : 2024-11-19
 * @author: AnRui
 * @return: uint8_t
 */
static uint8_t i2c_soft_resv() {
    // SDA应该交给从设备，主设备应该是输入状态
    uint8_t i, data;
    SET_SDA_MODE_IN();
    data = 0;
    for (i = 0; i < 8; i++) {
        SCL(0);// 给机会从设备准备数据
        DELAY();
        SCL(1);// 开始设置数据有效性
        DELAY();
        // data << 1;
        // data |= GET_SDA_PIN_STATE();
        data |= GET_SDA_PIN_STATE() << (7 - i);
        DELAY();
    }
    SCL(0);
    return data;
}

/**
 * @desc  : 主机发送ACK响应
 *           ______
 *          |      |
 *  SCL  ___|      |___
 *
 *      |
 *  SDA |
 *       ———————————————
 *
 *
 * @date  : 2024-11-19
 * @author: AnRui
 */
static void i2c_soft_send_ack() {
    // 主机获取SDA控制权，进入输出模式
    SET_SDA_MODE_OUT();
    // 拉低SDA
    SDA(0);
    SCL(0);
    DELAY();

    SCL(1);
    DELAY();
    SDA(1);
    SCL(0);
    DELAY();
}

/**
 * @desc  : 发送空响应
 *
 *           ______
 *          |      |
 *  SCL  ___|      |___
 *
 *       ———————————————
 *      |
 *  SDA |
 *
 * @date  : 2024-11-19
 * @author: AnRui
 */
static void i2c_soft_send_nack() {
    // 主机发送NACK响应
    // 主机获取SDA控制权，进入输出模式
    SET_SDA_MODE_OUT();

    // 拉高SDA
    SDA(1);
    SCL(0);
    DELAY();

    // 拉高SCL
    SCL(1);
    DELAY();

    // 拉低SCL
    SCL(0);
    SDA(1);
    DELAY();
}


/**
 * @desc  : 读数据
 * @date  : 2024-11-19
 * @author: AnRui
 * @param :  addr -{设备地址 7位}:
 * @param :  reg  -{寄存器地址}:
 * @param :  data -{保存数据的数组的指针}:
 * @param :  len  -{数组长度}:
 * @return: 0:正常  1 代表发送设备地址错误  2 代表发送寄存器地址错误 3 代表发送数据错误
 */
uint8_t i2c_soft_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {

    uint8_t waddr = addr << 1;
    uint8_t raddr = (addr << 1) | 1;

    i2c_soft_start();

    // 发送设备地址
    i2c_soft_send_data(waddr);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_DEVICE_ADDR; // 1 代表发送设备地址错误

    // 发送寄存器地址
    i2c_soft_send_data(reg);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_REGISTER_ADDR; // 1 代表发送寄存器地址错误

    i2c_soft_start();


    // 发送设备地址(读)
    i2c_soft_send_data(raddr);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_DEVICE_ADDR; // 1 代表发送设备地址错误
    /****** 接收数据 *****/
        // n条数据
            // for n - 1
            // 		接收数据
            // 		发送响应(ACK)

                // 第n条数据
            // 接收数据
            // 发送空响应(NACK)


    uint32_t i;
    for (i = 0; i < len; i++) {
        // 接收数据
        data [i] = i2c_soft_resv();
        if (i == len - 1) {
            // 如果是最后一条，发送NACK
            i2c_soft_send_nack();
        } else {
            // 如果不是最后一条，发送ACK
            i2c_soft_send_ack();
        }
    }
    /********************/

    i2c_soft_stop();
    return I2C_RESULT_OK;
}
/**
 * @desc  : I2C 写数据
 * @date  : 2024-11-20
 * @author: AnRui
 * @param :  addr -{设备地址 7位}:
 * @param :  reg  -{寄存器地址}:
 * @param :  data -{保存数据的数组的指针}:
 * @param :  len  -{数组长度}:
 * @return: 0:正常  1 代表发送设备地址错误  2 代表发送寄存器地址错误 3 代表发送数据错误
 */
uint8_t i2c_soft_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {
    uint8_t waddr = addr << 1;
    // 开始
    i2c_soft_start();
    // 发送设备地址
    i2c_soft_send_data(waddr);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_DEVICE_ADDR; // 1 代表发送设备地址错误
    // 发送寄存器地址
    i2c_soft_send_data(reg);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_REGISTER_ADDR; // 2 代表发送寄存器地址错误

    uint32_t i;
    for (i = 0;i < len; i++) {
        i2c_soft_send_data(data [i]);
        if (0 != i2c_soft_wait_ack()) return I2C_ERR_WRITE; // 3 代表发送数据错误
    }
    i2c_soft_stop();


    return I2C_RESULT_OK;

}




/**
 * @desc  : I2C 写数据 待偏置
 * @date  : 2024-11-20
 * @author: AnRui
 * @param :  addr -{设备地址 7位}:
 * @param :  reg  -{寄存器地址}:
 * @param :  data -{保存数据的数组的指针}:
 * @param :  len  -{数组长度}:
 * @return: 0:正常  1 代表发送设备地址错误  2 代表发送寄存器地址错误 3 代表发送数据错误
 */
uint8_t i2c_soft_write2(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t offset, uint32_t len) {
    uint8_t waddr = addr << 1;
    // 开始
    i2c_soft_start();
    // 发送设备地址
    i2c_soft_send_data(waddr);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_DEVICE_ADDR; // 1 代表发送设备地址错误

    // 发送寄存器地址
    i2c_soft_send_data(reg);
    // 等待ack
    if (0 != i2c_soft_wait_ack()) return I2C_ERR_REGISTER_ADDR; // 2 代表发送寄存器地址错误

    uint32_t i;
    for (i = 0;i < len; i++) {
        i2c_soft_send_data(data [i * offset]);
        if (0 != i2c_soft_wait_ack()) return I2C_ERR_WRITE; // 3 代表发送数据错误
    }
    i2c_soft_stop();


    return I2C_RESULT_OK;

}